########################################################################
#  Searx-qt - Lightweight desktop application for SearX.
#  Copyright (C) 2020  CYBERDEViL
#
#  This file is part of Searx-qt.
#
#  Searx-qt is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  Searx-qt is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
########################################################################

from PyQt5.QtWidgets import (
    QToolButton,
    QFrame,
    QHBoxLayout,
    QLabel,
    QMenu,
    QWidgetAction,
    QCheckBox
)

from PyQt5.QtCore import Qt
from PyQt5.QtGui import QCursor

from searxqt.translations import _


class Button(QToolButton):
    """ QToolButton with option to set text at creation.
    """
    def __init__(self, text="", parent=None):
        QToolButton.__init__(self, parent=parent)
        self.setText(text)


class CheckboxOptionsButton(QFrame):
    def __init__(self, labelName="", parent=None):
        QFrame.__init__(self, parent=parent)

        # Keep a list of disabled keys set with setKeyDisabled()
        self.__disabledKeys = []

        # Full label will be shown when this is True, character limited label
        # will be shown when this is False.
        self.__expanded = True
        # The character limit when the state of the label is collapsed (
        # self.__expanded = False)
        self.__collapsedCharLimit = 32

        layout = QHBoxLayout(self)
        layout.setContentsMargins(3, 3, 3, 3)

        self._labelName = labelName

        self.setFrameShape(QFrame.StyledPanel)

        openButton = Button("+", self)
        layout.addWidget(openButton, 0, Qt.AlignLeft | Qt.AlignTop)

        self._label = QLabel("", self)
        self._label.setWordWrap(True)
        layout.addWidget(self._label, 0, Qt.AlignLeft | Qt.AlignTop)

        openButton.clicked.connect(self.__toggleMenu)

        self.__genLabel()

    def saveSettings(self):
        """ Returns current widget state in a dict (serialised)
        """
        return {
            'expanded': self.__expanded
        }

    def loadSettings(self, data):
        self.__expanded = data.get('expanded', True)
        self.__genLabel()

    def keyDisabled(self, key):
        """
        @return: Returns True if the given key is disabled. Flase if
                 enabled.
        @rtype: bool
        """
        return bool(key in self.__disabledKeys)

    def setKeyDisabled(self, key):
        """ Disable the given key from being toggled.
        """
        self.__disabledKeys.append(key)

    def setKeyEnabled(self, key):
        """ Re-enable the given key so the user may toggle it again.
        """
        self.__disabledKeys.remove(key)

    def reGenerate(self):
        self.__genLabel()

    @property
    def labelName(self):
        return self._labelName

    @labelName.setter
    def labelName(self, name):
        self._labelName = name

    def __genLabel(self):
        """ Join all checked option names into a comma separated string.
        """
        str_ = _("All")

        checkedOptions = self.getCheckedOptionNames()
        if checkedOptions:
            str_ = ", ".join(checkedOptions)

        self._label.setToolTip(str_)

        # When the label state is collapsed (self.__expanded == false) the
        # character limit (self.__collapsedCharLimit) should be honored.
        if not self.__expanded and len(str_) > self.__collapsedCharLimit:
            str_ = str_[0:self.__collapsedCharLimit - 1]
            str_ += "..."

        self._label.setText("<b>" + self._labelName + "</b>: " + str_)

    def __addWidgets(self, menu):
        for key, name, state in self.getOptions():
            action = QWidgetAction(menu)
            widget = QCheckBox(name, menu)
            widget.setTristate(False)
            widget.setChecked(state)
            action.setDefaultWidget(widget)

            if key in self.__disabledKeys:
                widget.setEnabled(False)

            widget.stateChanged.connect(
                lambda state, key=key:
                    self.__checkBoxStateChanged(key, state)
            )

            menu.addAction(action)

    def __toggleMenu(self):
        menu = QMenu(self)
        menu.setStyleSheet("QMenu { menu-scrollable: 1; }")
        self.addCustomWidgetsTop(menu)
        self.__addWidgets(menu)
        menu.exec(QCursor.pos())

    def __toggleLabelState(self):
        """ Toggles the expanded/collapsed state of the label.
        """
        if self.__expanded:
            self.__expanded = False
        else:
            self.__expanded = True
        self.__genLabel()

    def __checkBoxStateChanged(self, key, state):
        self.optionToggled(key, state == 2)
        self.__genLabel()

    """ Methods below may be reimplemented.
    """
    def addCustomWidgetsTop(self, menu):
        pass

    def hasEnabledCheckedKeys(self):
        """ Returns True when there are checked options that are enabled for the
            user to uncheck. False when not. The add button it's context menu
            makes use of this to determine to show or not (if there are options
            to uncheck when `clear all` is triggered.)

            @rtype: bool
        """
        for key, name, state in self.getOptions():
            if state and not self.keyDisabled(key):
                return True
        return False

    def uncheckAllEnabledKeys(self):
        """ Unchecks all checked keys that are enabled.
        """
        for key, name, state in self.getOptions():
            if state and not self.keyDisabled(key):
                self.optionToggled(key, False)
        self.__genLabel()

    """ Methods below should be reimplemented.
    """
    def getCheckedOptionNames(self):
        """ Should return a list with checked option names. This will
        be used to generate the label.

        @return: should return a list with strings.
        @rtype: list
        """
        # Re-implement this!
        return []

    def getOptions(self):
        """ Should return a list with options tuple(key, name, state)

        This will be used to generate the options.
        """
        # Re-implement this!
        return []

    def optionToggled(self, key, state):
        # Re-implement to do stuff here.
        return

    """ QFrame re-implementations
    """
    def contextMenuEvent(self, event):
        menu = QMenu(self)

        # Uncheck all action
        clearAction = menu.addAction(_("Uncheck all"))
        if not self.hasEnabledCheckedKeys():
            clearAction.setEnabled(False)
        else:
            clearAction.triggered.connect(self.uncheckAllEnabledKeys)

        # Hide action (Hides this widget)
        hideAction = menu.addAction(_("Hide"))
        hideAction.triggered.connect(self.hide)

        menu.exec_(self.mapToGlobal(event.pos()))

    def mousePressEvent(self, event):
        """ Toggle the expanded/collapsed state of the label.
        """
        if event.button() == Qt.LeftButton:
            self.__toggleLabelState()
            event.accept()
        else:
            event.ignore()
